#include "os.h"

/*
 * The UART control registers are memory-mapped at address UART0. 
 * This macro returns the address of one of the registers.
 */
#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg))

/*
 * Reference
 * [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html
 */

/*
 * UART control registers map. see [1] "PROGRAMMING TABLE"
 * note some are reused by multiple functions
 * 0 (write mode): THR/DLL
 * 1 (write mode): IER/DLM
 */
#define RHR 0	// Receive Holding Register (read mode)
#define THR 0	// Transmit Holding Register (write mode)
#define DLL 0	// LSB of Divisor Latch (write mode)
#define IER 1	// Interrupt Enable Register (write mode)
#define DLM 1	// MSB of Divisor Latch (write mode)
#define FCR 2	// FIFO Control Register (write mode)
#define ISR 2	// Interrupt Status Register (read mode)
#define LCR 3	// Line Control Register
#define MCR 4	// Modem Control Register
#define LSR 5	// Line Status Register
#define MSR 6	// Modem Status Register
#define SPR 7	// ScratchPad Register

/*
 * POWER UP DEFAULTS
 * IER = 0: TX/RX holding register interrupts are both disabled
 * ISR = 1: no interrupt penting
 * LCR = 0
 * MCR = 0
 * LSR = 60 HEX
 * MSR = BITS 0-3 = 0, BITS 4-7 = inputs
 * FCR = 0
 * TX = High
 * OP1 = High
 * OP2 = High
 * RTS = High
 * DTR = High
 * RXRDY = High
 * TXRDY = Low
 * INT = Low
 */

/*
 * LINE STATUS REGISTER (LSR)
 * LSR BIT 0:
 * 0 = no data in receive holding register or FIFO.
 * 1 = data has been receive and saved in the receive holding register or FIFO.
 * ......
 * LSR BIT 5:
 * 0 = transmit holding register is full. 16550 will not accept any data for transmission.
 * 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character.
 * ......
 */
#define LSR_RX_READY (1 << 0)
#define LSR_TX_IDLE  (1 << 5)

#define uart_read_reg(reg) (*(UART_REG(reg)))
#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v))

void uart_init()
{
	/* 禁用中断。*/
	uart_write_reg(IER, 0x00);

	/*
	 * 设置波特率。这里只是一个演示，如果我们关心除数的话可以这样做，
	 * 但对我们当前的用途（QEMU-virt）来说，这实际上并不起什么作用。
	 *
	 * 注意：除数寄存器 DLL（除数锁存器低位）和 DLM（除数锁存器高位）
	 * 与接收/发送寄存器以及中断使能寄存器具有相同的基地址。要改变该
	 * 基地址实际访问的是哪一组寄存器，我们需要通过在线路控制寄存器
	 *（LCR）的第 7 位（DLAB，除数锁存访问位）写入 1 来“打开除数锁存器”。
	 *
	 * 关于波特率的取值，可参考文档 [1] 中的 “BAUD RATE GENERATOR PROGRAMMING TABLE”。
	 * 当使用 1.8432 MHz 晶振并选择 38.4K 波特率时，对应的除数值为 3。
	 * 由于除数寄存器是 2 字节（16 位），因此需要将 3 (0x0003) 拆成两个字节：
	 * DLL 存储低字节，DLM 存储高字节。
	 */
	uint8_t lcr = uart_read_reg(LCR);
	uart_write_reg(LCR, lcr | (1 << 7));
	uart_write_reg(DLL, 0x03);
	uart_write_reg(DLM, 0x00);

	/*
	 * 继续设置异步数据通信格式：
	 * - 字长：8 位
	 * - 停止位数：当字长为 8 位时为 1 位
	 * - 无奇偶校验
	 * - 无断点控制（break control）
	 * - 关闭波特率除数锁存（禁用 DLAB）
	 */
	lcr = 0;
	uart_write_reg(LCR, lcr | (3 << 0));

	/*
	 * 使能接收中断。
	 */
	uint8_t ier = uart_read_reg(IER);
	uart_write_reg(IER, ier | (1 << 0));
}

int uart_putc(char ch)
{
	while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0);
	return uart_write_reg(THR, ch);
}

void uart_puts(char *s)
{
	while (*s) {
		uart_putc(*s++);
	}
}

int uart_getc(void)
{
	while (0 == (uart_read_reg(LSR) & LSR_RX_READY))
		;
	return uart_read_reg(RHR);
}

/*
 * 处理一次 UART 中断：由于有输入到达而触发，由 trap.c 调用。
 */
void uart_isr(void)
{
	uart_putc((char)uart_getc());
	/* 为了显示更美观，追加一个换行 */
	uart_putc('\n');
}
